package com.tyros.common.secure.codec;

import com.tyros.common.secure.CryptoException;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
 * 编码器
 *
 * @author zsy
 */
public interface Codec {

    /**
     * 解码
     *
     * @param encodeData 编码后的字符串
     * @return 解码后的二进制
     */
    byte[] decode(String encodeData);


    /**
     * 解码
     *
     * @param encodeData 编码后的二进制数组
     * @return 解码后的二进制
     */
    byte[] decode(byte[] encodeData);


    /**
     * 编码
     *
     * @param rawData 原始二进制内容
     * @return 编码后的字符串
     */
    String encode(byte[] rawData);

    /**
     * 编码
     *
     * @param rawData 原始二进制内容
     * @return 编码后的字符串
     */
    String encode(String rawData);

    /**
     * 字符串编码
     */
    Codec STRING = new Codec() {
        @Override
        public byte[] decode(String encodeData) {
            return encodeData.getBytes(StandardCharsets.UTF_8);
        }

        @Override
        public byte[] decode(byte[] encodeData) {
            return encodeData;
        }

        @Override
        public String encode(byte[] rawData) {
            return new String(rawData, StandardCharsets.UTF_8);
        }

        @Override
        public String encode(String rawData) {
            return encode(rawData.getBytes(StandardCharsets.UTF_8));
        }
    };

    /**
     * base64编码器
     */
    Codec BASE64 = new Codec() {
        @Override
        public byte[] decode(String encodeData) {
            return decode(encodeData.getBytes(StandardCharsets.UTF_8));
        }

        @Override
        public byte[] decode(byte[] encodeData) {
            return Base64.getDecoder().decode(encodeData);
        }

        @Override
        public String encode(byte[] rawData) {
            return Base64.getEncoder().encodeToString(rawData);
        }

        @Override
        public String encode(String rawData) {
            return encode(rawData.getBytes(StandardCharsets.UTF_8));
        }
    };


    /**
     * 十六进制编码器
     */
    Codec HEX = new Codec() {

        private final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};


        @Override
        public byte[] decode(byte[] encodeData) {
            return decode(new String(encodeData));
        }

        /**
         * 字节数组转十六进制字符串
         *
         * @param data byte[]
         * @return 十六进制字符
         */
        @Override
        public String encode(byte[] data) {
            final int len = data.length;
            final char[] out = new char[len << 1];
            for (int i = 0, j = 0; i < len; i++) {
                out[j++] = DIGITS[(0xF0 & data[i]) >>> 4];
                out[j++] = DIGITS[0x0F & data[i]];
            }
            return new String(out);
        }

        /**
         * 十六进制字符转二进制
         *
         * @param data byte[]
         * @return 16进制字符
         * @throws CryptoException 解码字符非十六进制
         */
        @Override
        public byte[] decode(String data) {
            char[] hexData = data.toLowerCase().toCharArray();
            int len = hexData.length;
            if ((len & 0x01) != 0) {
                throw new CryptoException("Odd number of characters.");
            }
            byte[] out = new byte[len >> 1];
            for (int i = 0, j = 0; j < len; i++) {
                int f = toDigit(hexData[j], j) << 4;
                j++;
                f = f | toDigit(hexData[j], j);
                j++;
                out[i] = (byte) (f & 0xFF);
            }
            return out;
        }

        @Override
        public String encode(String rawData) {
            return encode(rawData.getBytes(StandardCharsets.UTF_8));
        }

        /**
         * 将十六进制字符转换成一个整数
         *
         * @param ch    十六进制char
         * @param index 十六进制字符在字符数组中的位置
         * @return 一个整数
         * @throws CryptoException 当ch不是一个合法的十六进制字符时，抛出运行时异常
         */
        private int toDigit(char ch, int index) {
            int digit = Character.digit(ch, 16);
            if (digit == -1) {
                throw new CryptoException("Illegal hexadecimal character " + ch + " at index " + index);
            }
            return digit;
        }
    };
}
